//
//  FXDragable.swift
//  VibeMate
//
//  Created by 范晓鑫 on 2023/7/11.
//

import Foundation

/// 拖动行为，可设置拖动的过程的行为或位移
public protocol FXDragBehavior {
    /// 开始拖动，若有需要可以在此处记住原始位置
    func drag(start view: UIView)
    /// 在拖动过程中将目标视图偏移位置(跟随手势会一直调用)
    func drag(move view: UIView, offset: CGPoint)
    /// 拖动结束，若有需要可设置最终位置(如移动过程使用transform，但结束时使用autoLayout)
    func drag(end view: UIView)
    /// 取消拖动重置回原位置
    func drag(reset view: UIView)
}
public extension FXDragBehavior {
    func drag(start view: UIView) { }
    func drag(end view: UIView) { }
    func drag(reset view: UIView) { }
}
/// 拖动行为的命名空间，将全部实例放到此命名空间下面方便智能提示
public struct FXDragBehaviors { }

open class FXDragGestureRecognizer: UIPanGestureRecognizer {
    let dragHandler: DargHandler
    /// 变换位置的目标视图，默认为手势所在的View
    open var targetView: UIView? {
        get { return dragHandler.targetView }
        set { dragHandler.targetView = newValue }
    }
    /// 拖动行为
    open var behavior: FXDragBehavior {
        get { return dragHandler.behavior }
        set { dragHandler.behavior = newValue }
    }
    public init(behavior: FXDragBehavior) {
        self.dragHandler = .init(behavior: behavior)
        super.init(target: dragHandler, action: #selector(DargHandler.handleGestureRecognizer(_:)))
    }
}
extension FXDragGestureRecognizer {
    class DargHandler {
        /// 变换位置的目标视图，默认为手势所在的View
        var targetView: UIView?
        /// 拖动行为
        var behavior: FXDragBehavior
        init(behavior: FXDragBehavior) {
            self.behavior = behavior
        }
        @objc func handleGestureRecognizer(_ pan: FXDragGestureRecognizer) {
            if let view = self.targetView ?? pan.view {
                let offset = pan.translation(in: view)
                pan.setTranslation(.zero, in: view)
                switch pan.state {
                case .began:
                    behavior.drag(start: view)
                case .changed:
                    behavior.drag(move: view, offset: offset)
                case .ended:
                    behavior.drag(move: view, offset: offset)
                    behavior.drag(end: view)
                case .cancelled, .failed:
                    behavior.drag(reset: view)
                default:
                    break
                }
            }
        }
    }
}

// MARK: 拖动定位器：可拖动视图

/// 拖动时视图跟着动
public protocol FXDragLocator: FXDragBehavior {
    /// 边缘吸附距离，为nil则不吸附，默认吸附且间距为0
    var edgeSnapping: UIEdgeInsets? { get }
    /// edgeSnapping的值是否使用安全边距，默认全部方向
    var edgeSnappingSafeInsets: FXOptionalDirection { get }
    /// 是否只吸附边缘，即拖动到中间的位置也会移动到边缘(只针对edgeSnapping不为nil的情况)，默认不固定
    var edgeSnappingOnly: FXOptionalDirection { get }
    /// 获取视图的当前位置, 默认view.frame
    func frame(for view: UIView) -> CGRect
    /// 边缘吸附过程的回调，就算不吸附也会回调，此处可能需要做动画，默认调用move并添加动画
    func drag(edgeSnapping view: UIView, offset: CGPoint)
    /// 边缘吸附后的回调，就算不吸附也会回调
    func drag(edgeSnapped view: UIView)
}
public extension FXDragLocator {
    var edgeSnapping: UIEdgeInsets? { return .zero }
    var edgeSnappingSafeInsets: FXOptionalDirection { return .all }
    var edgeSnappingOnly: FXOptionalDirection { return .empty }
    func frame(for view: UIView) -> CGRect { return view.frame }
    
    func drag(end view: UIView) {
        let offset = self.edgeSnapOffset(for: view)
        self.drag(edgeSnapping: view, offset: offset)
        self.drag(edgeSnapped: view)
    }
    func drag(edgeSnapping view: UIView, offset: CGPoint) {
        UIView.animate(withDuration: 0.25) {
            self.drag(move: view, offset: offset)
            view.superview?.layoutIfNeeded()
        }
    }
    func drag(edgeSnapped view: UIView) { }
    /// 计算边缘吸附后的偏移
    func edgeSnapOffset(for view: UIView) -> CGPoint {
        if var edgeSnapping, let superview = view.superview {
            let bounds = superview.bounds
            if edgeSnappingSafeInsets.contains(.top) {
                edgeSnapping.top += superview.safeAreaInsets.top
            }
            if edgeSnappingSafeInsets.contains(.bottom) {
                edgeSnapping.bottom += superview.safeAreaInsets.bottom
            }
            if edgeSnappingSafeInsets.contains(.left) {
                edgeSnapping.left += superview.safeAreaInsets.left
            }
            if edgeSnappingSafeInsets.contains(.right) {
                edgeSnapping.right += superview.safeAreaInsets.right
            }
            // 安全区域
            let safeBounds = bounds.inset(by: edgeSnapping)
            let frame = frame(for: view)
            var offset: CGPoint = .zero
            if frame.minX < safeBounds.minX { // 超出左边缘
                offset.x = safeBounds.minX - frame.minX
            } else if frame.maxX > safeBounds.maxX { // 超出右边缘
                offset.x = safeBounds.maxX - frame.maxX
            }
            // 在边缘内部需要计算是否需要吸附
            if edgeSnappingOnly.contains(.left) && edgeSnappingOnly.contains(.right) {
                let center = safeBounds.size.width / 2
                let current = frame.size.width / 2 + frame.minX
                if current <= center { // 向左
                    offset.x = safeBounds.minX - frame.minX
                } else { // 向右
                    offset.x = safeBounds.maxX - frame.maxX
                }
            } else if edgeSnappingOnly.contains(.left) {
                offset.x = safeBounds.minX - frame.minX
            } else if edgeSnappingOnly.contains(.right) {
                offset.x = safeBounds.maxX - frame.maxX
            }
            if frame.minY < safeBounds.minY { // 超出上边缘
                offset.y = safeBounds.minY - frame.minY
            } else if frame.maxY > safeBounds.maxY{ // 超出下边缘
                offset.y = safeBounds.maxY - frame.maxY
            }
            // 在边缘内部需要计算是否需要吸附
            if edgeSnappingOnly.contains(.top) && edgeSnappingOnly.contains(.bottom) {
                let center = safeBounds.size.height / 2
                let current = frame.size.height / 2 + frame.minY
                if current <= center { // 向上
                    offset.y = safeBounds.minY - frame.minY
                } else { // 向下
                    offset.y = safeBounds.maxY - frame.maxY
                }
            } else if edgeSnappingOnly.contains(.top) {
                offset.y = safeBounds.minY - frame.minY
            } else if edgeSnappingOnly.contains(.bottom) {
                offset.y = safeBounds.maxY - frame.maxY
            }
            return offset
        }
        return .zero
    }
}

// MARK: 使用Frame重定位拖动位置

extension FXDragBehaviors {
    /// 拖动时视图跟着动
    public struct FrameLocator: FXDragLocator {
        public var edgeSnapping: UIEdgeInsets? = .zero
        public var edgeSnappingSafeInsets: FXOptionalDirection = .all
        public var edgeSnappingOnly: FXOptionalDirection = .empty
        /// 每次重置布局时调用
        public var frameSetter: (UIView, CGRect) -> Void = { $0.frame = $1 }
        public func drag(move view: UIView, offset: CGPoint) {
            let frame = view.frame.offsetBy(dx: offset.x, dy: offset.y)
            frameSetter(view, frame)
        }
    }
}

// MARK: 使用Transform重定位拖动位置

extension FXDragBehaviors {
    /// 拖动时视图跟着动
    public struct TransformLocator: FXDragLocator {
        public var edgeSnapping: UIEdgeInsets? = .zero
        public var edgeSnappingSafeInsets: FXOptionalDirection = .all
        public var edgeSnappingOnly: FXOptionalDirection = .empty
        public func drag(move view: UIView, offset: CGPoint) {
            view.transform = view.transform.translatedBy(x: offset.x, y: offset.y)
        }
    }
}
